﻿// Fill out your copyright notice in the Description page of Project Settings.


#include "AsyncBlueprintNode/AsyncSetTimer.h"
#include "Kismet/KismetSystemLibrary.h"


DEFINE_LOG_CATEGORY(LogAsyncAction);

//Storage all UAsyncSetTimer instance for destroy
static TMap<FString, UAsyncSetTimer*> TimerHandleInstMap;


UAsyncSetTimer::UAsyncSetTimer()
{
	WorldContextObject = nullptr;

	//if (HasAnyFlags(RF_ClassDefaultObject) == false)
	//{
	//	AddToRoot();
	//}
	// Helper message to track object instance
	// UE_LOG(LogAsyncAction, Log, TEXT("UAsyncSetTimer::UAsyncSetTimer(): Async_SetTimer object [%s] is being created."), *this->GetName());
}


UAsyncSetTimer::~UAsyncSetTimer()
{
	// Helper message to track object instance
	// UE_LOG(LogAsyncAction, Log, TEXT("UAsyncSetTimer::~UAsyncSetTimer(): Async_SetTimer object is being deleted."));
}


UAsyncSetTimer* UAsyncSetTimer::SetTimer(const UObject* WorldContextObject, float Time, bool bLooping, float InitialStartDelay /*= 0.f*/, float InitialStartDelayVariance /*= 0.f*/)
{
	if (!WorldContextObject)
	{
		FFrame::KismetExecutionMessage(TEXT("Invalid WorldContextObject. Cannot execute Set Timer."), ELogVerbosity::Error);
		return nullptr;
	}
	/** Based on UKismetSystemLibrary::K2_SetTimer() */
	InitialStartDelay += FMath::RandRange(-InitialStartDelayVariance, InitialStartDelayVariance);
	if (Time <= 0.f || ((Time + InitialStartDelay) - InitialStartDelayVariance) < 0.f)
	{
		FFrame::KismetExecutionMessage(TEXT("SetTimer passed a negative or zero time.  The associated timer may fail to fire!  If using InitialStartDelayVariance, be sure it is smaller than (Time + InitialStartDelay)."), ELogVerbosity::Warning);
		return nullptr;
	}

	UAsyncSetTimer* AsyncNode = NewObject<UAsyncSetTimer>();
	AsyncNode->WorldContextObject = WorldContextObject;

	FTimerDynamicDelegate Delegate;
	Delegate.BindUFunction(AsyncNode, FName("CompletedEvent"));
	AsyncNode->TimerHandle = AsyncNode->SetTimerDelegate(Delegate, Time, bLooping, InitialStartDelay, InitialStartDelayVariance);

	// UBlueprintAsyncActionBase是UObject派生类，若不进行对象管理，则会自动被回收；
	// 生成后，调用RegisterWithGameInstance()，防止自动回收
	// Call to globally register this object with a game instance, it will not be destroyed until SetReadyToDestroy is called
	AsyncNode->RegisterWithGameInstance((UObject*)WorldContextObject);
	TimerHandleInstMap.Emplace(AsyncNode->TimerHandle.ToString(), AsyncNode);

	return AsyncNode;
}

/** Based on UKismetSystemLibrary::K2_ClearAndInvalidateTimerHandle */
void UAsyncSetTimer::ClearAndInvalidateTimerHandle(UObject* WorldContextObject, UPARAM(ref)FTimerHandle& Handle)
{
	if (Handle.IsValid())
	{
		// Get UAsyncSetTimer object
		UAsyncSetTimer* Node = *TimerHandleInstMap.Find(Handle.ToString());
		if (Node)
		{
			// ready to destroy this UAsyncSetTimer object
			Node->SetReadyToDestroy();
			//remove from map
			TimerHandleInstMap.Remove(Handle.ToString());
		}
		//Clears a set timer.
		UKismetSystemLibrary::K2_ClearAndInvalidateTimerHandle(WorldContextObject, Handle);
	}
}


/** Based on UKismetSystemLibrary::K2_SetTimerDelegate() */
FTimerHandle UAsyncSetTimer::SetTimerDelegate(FTimerDynamicDelegate Delegate, float Time, bool bLooping, float InitialStartDelay /*= 0.f*/, float InitialStartDelayVariance /*= 0.f*/)
{
	FTimerHandle Handle;
	if (Delegate.IsBound())
	{
		const UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
		if (World)
		{
			InitialStartDelay += FMath::RandRange(-InitialStartDelayVariance, InitialStartDelayVariance);
			if (Time <= 0.f || ((Time + InitialStartDelay) - InitialStartDelayVariance) < 0.f)
			{
				FFrame::KismetExecutionMessage(TEXT("SetTimer passed a negative or zero time.  The associated timer may fail to fire!  If using InitialStartDelayVariance, be sure it is smaller than (Time + InitialStartDelay)."), ELogVerbosity::Warning);
			}

			FTimerManager& TimerManager = World->GetTimerManager();
			Handle = TimerManager.K2_FindDynamicTimerHandle(Delegate);
			TimerManager.SetTimer(Handle, Delegate, Time, bLooping, (Time + InitialStartDelay));
		}
	} else
	{
		// UE_LOG(LogBlueprintUserMessages, Warning, TEXT("SetTimer passed a bad function (%s) or object (%s)"), *Delegate.GetFunctionName().ToString(), *GetNameSafe(Delegate.GetUObject()));
	}

	return Handle;
}


void UAsyncSetTimer::Activate()
{
	if (!WorldContextObject)
	{
		FFrame::KismetExecutionMessage(TEXT("Invalid WorldContextObject. Cannot execute Set Timer."), ELogVerbosity::Error);
		return;
	}

	// call Then delegate binding event
	Then.Broadcast(TimerHandle);
}


void UAsyncSetTimer::CompletedEvent()
{
	Completed.Broadcast(TimerHandle);
}
